// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "touch_noise_filter/input_event_filter.h"

#include <linux/input.h>
#include <stdio.h>

#include <base/basictypes.h>

namespace touch_noise_filter {

InputEventFilter::InputEventFilter(NoiseFilter* filter)
    : current_touching_(0), prev_touching_(0), filter_(filter),
      current_slot_(0) {
  for (size_t i = 0; i < arraysize(frames_[0].fingers_); i++) {
    PrevFrame()->fingers_[i].tracking_id_ = -1;
    CurrentFrame()->fingers_[i].tracking_id_ = -1;
  }
}

namespace {
#define SETSTR(type_num, code_num) \
  do { \
    if ((ev)->type == type_num) { \
      type_str = #type_num; \
      if ((ev)->code == code_num) { \
        code_str = #code_num; \
      } \
    } \
  } while (0)

double TimevalToDouble(const struct timeval& tv) {
  // printf("%ld usec\n", tv.tv_usec);
  return static_cast<double>(tv.tv_sec) + static_cast<double>(tv.tv_usec) /
    1000000.0;
}
#undef SETSTR
}  // namespace {}

void InputEventFilter::HandleInputEvent(const struct input_event* ev) {
  static const __u16 kMinKey = ABS_MT_TOUCH_MAJOR;
  
  static __s32 Finger::* const member_map[] = {
    // The first one must correspond w/ kMinKey above, and they must be in
    // numerical order.
    NULL,  // ABS_MT_TOUCH_MAJOR
    NULL,  // ABS_MT_TOUCH_MINOR
    NULL,  // ABS_MT_WIDTH_MAJOR
    NULL,  // ABS_MT_WIDTH_MINOR
    NULL,  // ABS_MT_ORIENTATION
    &Finger::x_pos_,  // ABS_MT_POSITION_X
    &Finger::y_pos_,  // ABS_MT_POSITION_Y
    NULL,  // ABS_MT_TOOL_TYPE
    NULL,  // ABS_MT_BLOB_ID
    &Finger::tracking_id_,  // ABS_MT_TRACKING_ID
    NULL,  // ABS_MT_PRESSURE
  };

  switch (ev->type) {
    case EV_ABS: {
      switch (ev->code) {
        case ABS_MT_SLOT:
          current_slot_ = ev->value;
          break;
        case ABS_MT_TRACKING_ID:
          if (ev->value >= 0)
            current_touching_ |= (1ULL << current_slot_);
          else
            current_touching_ &= ~(1ULL << current_slot_);
          // fallthrough
        default:
          if (ev->code < kMinKey ||
              ev->code >= kMinKey + arraysize(member_map) ||
              member_map[ev->code - kMinKey] == NULL)
            break;
          CurrentFrame()->fingers_[
              current_slot_].*member_map[ev->code - kMinKey] = ev->value;
          break;
      }
      break;
    }
    case EV_SYN: {
      for (size_t i = 0; i < arraysize(frames_[0].fingers_); i++) {
        Finger* cur_finger = &CurrentFrame()->fingers_[i];
        // New fingers should have their canceled_ bit cleared
        if ((current_touching_ & (1ULL << i)) &&
            !(prev_touching_ & (1ULL << i)))
          cur_finger->canceled_ = false;
      }
      CurrentFrame()->timestamp_ = TimevalToDouble(ev->time);
      filter_->FilterFrame(PrevFrame(), CurrentFrame(), TOUCH_NOISE_MAX_SLOTS);
      memcpy(PrevFrame(), CurrentFrame(), sizeof(Frame));
      prev_touching_ = current_touching_;
      break;
    }
  }
}

void InputEventFilter::GetCanceledTouches(uint64_t* slots_mask) {
  for (size_t i = 0; i < arraysize(frames_[0].fingers_); i++) {
    if (CurrentFrame()->fingers_[i].canceled_) {
      __s32 tracking_id = CurrentFrame()->fingers_[i].tracking_id_;
      if (tracking_id == -1)
        tracking_id = PrevFrame()->fingers_[i].tracking_id_;
      if (tracking_id != -1)
        Log("TrackingID %d cancelled", tracking_id);
      (*slots_mask) |= 1 << i;
      // If the finger was released, we can lose the cancel bit
      if (!(prev_touching_ & (1ULL << i)))
        CurrentFrame()->fingers_[i].canceled_ = 0;
    }
  }
}

}  // namespace touch_noise_filter
